package com.io.nio;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;

/**
 * 缓冲器的细节。 P595
 * Buffer中的索引：
 * 	capacity：缓冲区数据的总长度
 * 	position：下一个要操作的数据元素的位置
 * 	limit：缓冲区数组中不可操作的下一个元素的位置，limit<=capacity
 * 	mark：用于标记当前position的前一个位置或者默认是0
 *
 * 首先通过ByteBuffer.allocate()方法分配了一段内存空间，作为缓存，allocate方法对缓存自动清零。
 * position表示当前可读写的指针，如果是向ByteBuffer对象中写入一个字节，那么就会向position所指向的地址写入这个字节，如果是从ByteBuffer读出一个字节，那么就会读出position所指向的地址读出这个字节，读写完成后，position加1。
 * limit是可以读写的边界，当position到达limit时，就表示将ByteBuffer中的内容读完，或者将ByteBuffer写满了。
 * capacity是这个ByteBuffer的容量，上面的程序中调用ByteBuffer.allocate(128)就表示创建了一个容量为capacity字节的ByteBuffer对象。
 * 调用ByteBuffer.flip()方法是因为在向ByteBuffer写入数据后，position为缓冲区中刚刚读入的数据的最后一个字节的位置，flip方法将limit值置为position值，position置0，这样在调用get*()方法从ByteBuffer中取数据时就可以取到ByteBuffer中的有效数据。
 * ByteBuffer.clear()方法为下次从管道中读取数据做准备，但是调用clear方法并不将缓冲区的数据清空，而是设置position，mark，limit这三个变量的值，JDK中clear方法的代码如下：position = 0;limit = capacity;mark = -1;
 * 这个方法命名给人的感觉就是将数据清空了，但是实际上却不是的，它并没有清空缓冲区中的数据，而至重置了对象中的三个索引值，如果不清空的话，假设此次该ByteBuffer中的数据是满的，下次读取的数据不足以填满缓冲区，那么就会存在上一次已经处理的的数据，所以在判断缓冲区中是否还有可用数据时，使用ByteBuffer.hasRemaining()方法，在JDK中，这个方法的代码如下：
		public final boolean hasRemaining() {
		    return position < limit;
		}
 * compact，对于ByteBuffer，其子类HeapByteBuffer的compact方法实现是这样的：
		public ByteBuffer compact() {
		    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
		    position(remaining());
		    limit(capacity());
		    return this;
		}
 * 如果position()方法返回当前缓冲区中的position值，remaining()方法返回limit与position这段区间的长度，JDK中的remaining()方法代码如下
		public final int remaining() {
		    return limit - position;
		}
	所以compact()方法中第一条语句作用是将数组hb当前position所指向的位置开始复制长度为limit-position的数据到hb数组的开始处。
	在compact函数中，接着将当前的缓冲区的position索引置为limit-position，limit索引置为缓冲区的容量，这样调用compact方法中就可以将缓冲区的有效数据全部移到缓冲区的首部，而position指向下一个可写位置。
比如刚刚创建一个ByteBuffer对象buff时，position=0，limit=capacity，那么此时调用buff.hasRemaining()则会返回true，这样来判断缓冲区中是否有数据是不行的，因为此时缓冲区中的存储的全部是0，但是调用一次compact()方法就可以将position置为limit值，这样再通过buff.hasRemaining()就会返回false，可以与后面的逻辑一起处理了。
 */
public class BufferDetail {

	public static void sym(CharBuffer buffer) {
		// hasRemaining若有介于position和limit之间的元素，则返回true
		while (buffer.hasRemaining()) {
			// mark：将mark设置为position，初始position为0
			buffer.mark();
			// get：把当前position的字符保存到变量c1，同时position指向下一个位置
			char c1 = buffer.get();
			char c2 = buffer.get();
			// reset：把position的值设置为mark
			buffer.reset();
			// put：把c2写入当前position的位置，同时position指向下一个位置
			buffer.put(c2).put(c1);
		}
	}

	public static void test(){
		String str = "helloWorld";
        ByteBuffer buff  = ByteBuffer.wrap(str.getBytes());
        //初始情况下mark是指向第一个元素之前的的即-1,postion为指向第一个元素为0.而Limit是被赋值为byte[]的长度。因此这就是打印结果的第一行。
        System.out.println("position:"+buff.position()+"\t limit:"+buff.limit());
        //读取两个字节
        buff.get();
        buff.get();
        //当我们连续调用两次get()方法获得两个个字节，每次调用都会触发position++操作，那么此时position就会移动到index = 2的的地方，而这个时候Limit和mark是不会发生变化的。如果将读取的两个字节打印会是H和E，因此执行结果第二行会有position:2  limit:10结果.
        System.out.println("position:"+ buff.position()+"\t limit:"+buff.limit());
        buff.mark();
        //读取完毕后我们使用mark，这个时候mark会从-1移动到2和position指向同一个元素，可以看见Limit是不会发生改变的。
        System.out.println("position:"+buff.position()+"\t limit:"+buff.limit());
        buff.flip();
        //使用了mark标记的当前的position后，如果们调用flip，意思是做好读取的准备，这个时候Limit就会指向position的位置，并将mark和position还原为初始值。limit当前的就为2，就是说当前可以读的字节数是2。
        System.out.println("position:"+buff.position()+"\t limit:"+buff.limit());
        System.out.println((char)buff.get()+""+(char)buff.get());
        //如果你把上面的代码换成，会抛异常
        //原因是limit的含义就像一个窗口，你当前能读到的数据就是当前窗口限制的(本例中即为2),如果这个窗口之外的所有元素都是不可读的。
        // System.out.println((char)buff.get()+""+(char)buff.get()+""+(char)buff.get());

        //mark的方法，该方法设置mark索引为position的值
        //reset方法 是把当前位置position设置为当前mark
        //rewind是将mark重置为-1,position重置为0;
        //clear方法是真正的重置，将mark=-1,position=0,limit=capacity(即当前buffer的容量)
	}

	public static void main(String[] args) {
		char[] data = "UsingBuffers".toCharArray();
		//通过allocate(11)可以创建了一个11个byte的数组缓冲区，初始状态position为0，capacity和limit默认都是数组长度。
		//当我们写入5个字节时，position指向5，capacity和limit不变。
		ByteBuffer bb = ByteBuffer.allocate(data.length * 2);
		CharBuffer cb = bb.asCharBuffer();
		cb.put(data);

		System.out.println(cb.rewind());
		sym(cb);
		System.out.println(cb.rewind());
		sym(cb);
		System.out.println(cb.rewind());

		System.out.println("==============");
		test();

		ByteBuffer bb2 = ByteBuffer.allocate(1024);
		IntBuffer ib = bb2.asIntBuffer(); // view buffer

		// 存储int的数组
		ib.put(new int[]{11, 42, 47, 99, 143, 811, 1016});

		// 绝对位置读写
		//System.out.println(ib.get(3));

		System.out.println("*******************************");
		System.out.println(ib.limit());
		ib.put(3, 1811);
		ib.flip();
		System.out.println(ib.limit());
		System.out.println("*******************************");
		//ib.rewind();

		while(ib.hasRemaining()){
			int i = ib.get();
			System.out.println(i);
		}

		//ByteBuffer类提供了一种char和byte之间的软转换，他们之间的转换不需要编码与解码，只是把一个16bit的char拆分为2个8bit的byte表示，它们的实际值并没有被修改，仅仅是数据的类型做了转换。
		ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024);
		ByteBuffer byteBuffer = heapByteBuffer.putChar('c');
	}

}
